//===----------------------------------------------------------------------===//
//
//                         BusTub
//
// buffer_pool_manager_instance.cpp
//
// Identification: src/buffer/buffer_pool_manager.cpp
//
// Copyright (c) 2015-2021, Carnegie Mellon University Database Group
//
//===----------------------------------------------------------------------===//

#include "buffer/buffer_pool_manager_instance.h"

#include "common/macros.h"

namespace bustub {

BufferPoolManagerInstance::BufferPoolManagerInstance(size_t pool_size, DiskManager *disk_manager,
                                                     LogManager *log_manager)
    : BufferPoolManagerInstance(pool_size, 1, 0, disk_manager, log_manager) {}

BufferPoolManagerInstance::BufferPoolManagerInstance(size_t pool_size, uint32_t num_instances, uint32_t instance_index,
                                                     DiskManager *disk_manager, LogManager *log_manager)
    : pool_size_(pool_size),
      num_instances_(num_instances),
      instance_index_(instance_index),
      next_page_id_(instance_index),
      disk_manager_(disk_manager),
      log_manager_(log_manager) {
  BUSTUB_ASSERT(num_instances > 0, "If BPI is not part of a pool, then the pool size should just be 1");
  BUSTUB_ASSERT(
      instance_index < num_instances,
      "BPI index cannot be greater than the number of BPIs in the pool. In non-parallel case, index should just be 1.");
  // We allocate a consecutive memory space for the buffer pool.
  pages_ = new Page[pool_size_];
  replacer_ = new LRUReplacer(pool_size);

  // Initially, every page is in the free list.
  for (size_t i = 0; i < pool_size_; ++i) {
    free_list_.emplace_back(static_cast<int>(i));
  }
}

BufferPoolManagerInstance::~BufferPoolManagerInstance() {
  delete[] pages_;
  delete replacer_;
}

bool BufferPoolManagerInstance::FlushPgImp(page_id_t page_id) {
  // Make sure you call DiskManager::WritePage!
  latch_.lock();
  // 当前页不在池中
  if (!page_table_.count(page_id)) {
    latch_.unlock();
    return false;
  }

  Page *page = pages_ + page_table_[page_id];
  latch_.unlock();
  disk_manager_->WritePage(page_id, page->data_);
  return true;
}

void BufferPoolManagerInstance::FlushAllPgsImp() {
  // You can do it!
  for (auto [page_id, _] : page_table_) {
    FlushPage(page_id);
  }
}

Page *BufferPoolManagerInstance::NewPgImp(page_id_t *page_id) {
  // 0.   Make sure you call AllocatePage!
  // 1.   If all the pages in the buffer pool are pinned, return nullptr.
  // 2.   Pick a victim page P from either the free list or the replacer. Always pick from the free list first.
  // 3.   Update P's metadata, zero out memory and add P to the page table.
  // 4.   Set the page ID output parameter. Return a pointer to P.

  // 取得新page_id
  *page_id = AllocatePage();
  frame_id_t frame_id = -1;

  latch_.lock();
  // 从空闲链表中占位置
  if (free_list_.size()) {
    frame_id = free_list_.front();
    free_list_.pop_front();
  } else {  // 从lru链表抢位置
    if (!Victim(&frame_id)) {
      latch_.unlock();
      return nullptr;
    }
  }
  // 插入page_table，占位置
  page_table_[*page_id] = frame_id;
  // 更新page内容
  Page *page = pages_ + frame_id;
  page->pin_count_ = 1;
  page->page_id_ = *page_id;
  page->is_dirty_ = false;
  page->ResetMemory();
  latch_.unlock();
  return page;
}

Page *BufferPoolManagerInstance::FetchPgImp(page_id_t page_id) {
  // 1.     Search the page table for the requested page (P).
  // 1.1    If P exists, pin it and return it immediately.
  // 1.2    If P does not exist, find a replacement page (R) from either the free list or the replacer.
  //        Note that pages are always found from the free list first.
  // 2.     If R is dirty, write it back to the disk.
  // 3.     Delete R from the page table and insert P.
  // 4.     Update P's metadata, read in the page content from disk, and then return a pointer to P.
  frame_id_t frame_id = -1;
  Page *page = nullptr;

  latch_.lock();

  // 如果为有效页，则pin+1，返回
  if (page_table_.count(page_id)) {
    frame_id = page_table_[page_id];
    page = pages_ + frame_id;
    // 若在lru中，则取出
    replacer_->Pin(frame_id);
    ++page->pin_count_;
    latch_.unlock();
    return page;
  }
  // 无获取的页已经被换, 出效页，找位置frame占住
  if (free_list_.size()) {
    frame_id = free_list_.front();
    free_list_.pop_front();
  } else {  // 牺牲一个页（位于lru链表中的页都是 pin_count==0)
    if (!Victim(&frame_id)) {
      latch_.unlock();
      return nullptr;
    }
  }
  page = pages_ + frame_id;
  page_table_[page_id] = frame_id;
  ++page->pin_count_;
  page->page_id_ = page_id;
  latch_.unlock();
  disk_manager_->ReadPage(page_id, page->data_);
  return page;
}

bool BufferPoolManagerInstance::DeletePgImp(page_id_t page_id) {
  // 0.   Make sure you call DeallocatePage!
  // 1.   Search the page table for the requested page (P).
  // 1.   If P does not exist, return true.
  // 2.   If P exists, but has a non-zero pin-count, return false. Someone is using the page.
  // 3.   Otherwise, P can be deleted. Remove P from the page table, reset its metadata and return it to the free list.
  latch_.lock();

  if (!page_table_.count(page_id)) {
    latch_.unlock();
    return true;
  }

  // 获取frame，从page_table中删除
  frame_id_t frame_id = page_table_[page_id];
  Page *page = pages_ + frame_id;

  // 还有使用者，不在lru中
  if (page->pin_count_ > 0) {
    latch_.unlock();
    return false;
  }

  // 若在lru中，则删除
  replacer_->Pin(frame_id);

  // 加入空闲链表
  free_list_.push_back(frame_id);
  page_table_.erase(page_id);

  // 清空内容，设置无效id
  page->ResetMemory();
  page->page_id_ = INVALID_PAGE_ID;
  page->pin_count_ = 0;
  page->is_dirty_ = false;

  DeallocatePage(page_id);
  latch_.unlock();

  return true;
}

bool BufferPoolManagerInstance::UnpinPgImp(page_id_t page_id, bool is_dirty) {
  latch_.lock();
  frame_id_t frame_id = page_table_[page_id];
  Page *page = pages_ + frame_id;

  if (page->pin_count_ == 0) {
    latch_.unlock();
    return false;
  }

  if (is_dirty) {
    page->is_dirty_ = is_dirty;
  }

  if (--page->pin_count_ == 0) {
    // 若lru满了,则牺牲最后一页并删除
    if (replacer_->Size() == pool_size_) {
      frame_id_t vic_frame;
      Victim(&vic_frame);
      latch_.unlock();
      DeletePgImp((pages_ + page_table_[vic_frame])->GetPageId());
    }
    // 将该页加入lru
    replacer_->Unpin(frame_id);
  }

  latch_.unlock();
  return true;
}

page_id_t BufferPoolManagerInstance::AllocatePage() {
  const page_id_t next_page_id = next_page_id_;
  next_page_id_ += num_instances_;
  ValidatePageId(next_page_id);
  return next_page_id;
}

// page_id分配：
/* num_instances_ = 3
 * instance_index_ :  0           1          2
 *                   0 3 6      1 4 7       2 5 8
 */
void BufferPoolManagerInstance::ValidatePageId(const page_id_t page_id) const {
  assert(page_id % num_instances_ == instance_index_);  // allocated pages mod back to this BPI
}

bool BufferPoolManagerInstance::Victim(frame_id_t *frame_id) {
  // 从lru中取出
  if (!replacer_->Victim(frame_id)) {
    return false;
  }
  Page *vic_page = pages_ + *frame_id;
  // 若page脏则写磁盘
  if (vic_page->IsDirty()) {
    latch_.unlock();
    disk_manager_->WritePage(vic_page->page_id_, vic_page->data_);
    latch_.lock();
  }
  vic_page->is_dirty_ = false;
  // 该页被换出，内容失效
  page_table_.erase(vic_page->page_id_);
  vic_page->page_id_ = INVALID_PAGE_ID;

  return true;
}

}  // namespace bustub
